#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <stack>
#include <boost/format.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/logic/tribool.hpp>

#include "adventure-loop.hh"
#include "p-machine.hh"
#include "read_data.hh"
#include "simple_reader.hh"
#include "editor.hh"

using namespace std;
using boost::format;
using boost::tribool;
using boost::indeterminate;


bool do_event(Adventure &adv, const vector<Event> &events) {
  for(unsigned long i = 0; i < adv.events.size(); ++i) {
    const Event *p = &(adv.events[i]);
    //cout << format("event %p (i=%d): location=%d,verb=%d,adverb=%d,obj1=%d,adj1=%d,obj2=%d,adj2=%d\n") % p % i % p->location % p->verb % p->adverb % p->obj1 % p->adj1 % p->obj2 % p->adj2;
    if(
       (p->location == adv.here || p->location < 0) &&
       ((p->verb == adv.verb) || (p->verb == EVENT_ANY && adv.verb >= 0)) &&
       ((p->adverb == adv.adverb) || (p->adverb == EVENT_ANY && adv.adverb >= 0)) &&
       ((p->obj1 == adv.obj1) || (p->obj1 == EVENT_ANY && adv.obj1 >= 0)) &&
       ((p->obj2 == adv.obj2) || (p->obj2 == EVENT_ANY && adv.obj2 >= 0)) &&
       ((p->adj1 == adv.adj1) || (p->adj1 == EVENT_MAYBE) || (p->adj1 == EVENT_ANY && adv.adj1 >= 0)) &&
       ((p->adj2 == adv.adj2) || (p->adj1 == EVENT_MAYBE) || (p->adj2 == EVENT_ANY && adv.obj1 >= 0))
       ) {
      //cout << p << " matches.\n";
      if(!p->pcode.empty()) {
	vector<pcode_array>::const_iterator pcptr;
	bool trace = false;
	try {
	  for(pcptr = p->pcode.begin(); pcptr != p->pcode.end(); ++pcptr) {
	    int r = prun(adv, *pcptr, trace);
	    if(r == 1) {
	      //prun returned true!
	      return true;
	    }
	  }
	}
	catch(...) {
	  const pcode_array *arrptr = &*pcptr;
	  cerr << "Exception in pcode (" << arrptr << "): " << hex;
	  std::copy(arrptr->begin(), arrptr->end(), std::ostream_iterator<int>(cerr, " "));
	  cerr << dec << endl;
	  throw;
	}
      }
    }
  }
  return false; //Nothing done
}

/*! \brief move the player
 *
 * Move the player according to the verb found in the adv structure.
 *
 * \param adv adventure object
 * \return true if movement successful or false if movement not successful, indeterminate: no movement verb
 */
tribool do_movement(Adventure& adv) {
  if(adv.movement_verbs.find(adv.verb) != adv.movement_verbs.end()) {
    ExitMap::const_iterator it = adv.locations[adv.here].exits.find(adv.verb);

    if(it != adv.locations[adv.here].exits.end()) {
      adv.here = it->second;
      return true;
    }
    return false;
  }
  return indeterminate;
}

/*
 * The main loop should do the following:
 * high priority conditions, this includes:
 *  - description of objects, exits, etc.
 *  - location is not yet visited!
 *  - returns a value which is 0 (normal continue) or has set one of the following bits, see skip
 * get input (0x1 if skip)
 * parse input, print parse error if necessary
 * (low priority) conditions: (0x2 if skip)
 *  - does print all the nice messages
 *  - handles events
 *  - handles special events (must return if movement is to be done!)
 * move player according to movement verbs given and the status (0x4 skip)
 * loop according to status
 *
 * status changes:
 *  - player can die
 *  - player can exit/quit the game
 *  - player wants to save/load the game
 *  - player needs to be moved
 */


Adventure &adventure_loop(Adventure &adv) {
  string line, tmp;
  tribool last_verb_has_moved = true;

  do {
    string verbstr;
    ++adv.turns;
    adv.verb = -1;
    adv.adverb = -1;
    adv.obj1 = -1;
    adv.adj1 = -1;
    adv.obj2 = -1;
    adv.adj2 = -1;

    /* High priority events. */
    for(auto it = adv.hp_events.begin(); it != adv.hp_events.end(); ++it) {
      int ret = prun(adv, *it);
      if(ret == 1) break;
      //We care only if true is returned...: if(ret == -1) cerr << "Event did neither return true or false!\n";
    }
    cout << endl << "? " << flush;
    getline(cin, line);
    if(line.empty()) continue;
    if(line.size() > 0 && line[0] == '!') {
      edit(adv, line.substr(1));
      continue;
    }
    boost::to_lower(line);
    istringstream in(line);
    in >> verbstr;
    adv.verb = adv.verbs.find(verbstr) != adv.verbs.end() ? adv.verbs.find(verbstr)->second : -1;
    while(in >> tmp) {
      int i;
      //Find *first* adjective. If we have an object1 already it must
      //be the second adjective, see later...
      if(adv.adj1 < 0 && adv.obj1 < 0) {
	auto it = adv.adjectives.find(tmp);
	if(it != adv.adjectives.end()) {
	  adv.adj1 = it->second;
	  continue;
	}
      }
      if(adv.adj2 < 0) { //Second adjective?
	auto it = adv.adjectives.find(tmp);
	if(it != adv.adjectives.end()) {
	  adv.adj2 = it->second;
	  continue;
	}
      }
      //Find the objects now...
      if(adv.obj1 < 0) { //We do not have the first object.
	if((i = adv.find_object(tmp, adv.adj1)) >= 0) {
	  adv.obj1 = i;
	  continue;
	}
      }
      if(adv.obj2 < 0) {
	if((i = adv.find_object(tmp, adv.adj2)) >= 0) {
	  adv.obj2 = i;
	  continue;
	}
      }
      //ignore too many objects
    }
    if(adv.verb < 0) {
      cout << "I do not know how to `" << verbstr << "`.\n";
    } else {
      cout << format("verb=%d,adverb=%d,obj1=%d,adj1=%d,obj2=%d,adj2=%d\n") % adv.verb % adv.adverb % adv.obj1 % adv.adj1 % adv.obj2 % adv.adj2;
      int old_here = adv.here;
      bool event_done = do_event(adv, adv.events);
      /* Do event may change the room and we do not want to register
       * the new room as visited as the old room has been
       * visited... */
      adv.locations.at(old_here).visited = true;
      if(!event_done) {
	last_verb_has_moved = do_movement(adv);
	if(indeterminate(last_verb_has_moved)) {
	  cout << "I do not understand!\n";
	} else if(!last_verb_has_moved) {
	  cout << "You can not go in that direction!\n";
	}
      }
    }
    {
      ofstream out("SIMPLE");
      write_to_file(adv, out);
    }
  } while(!adv.switches.at(ADVSWITCH_GAME_ENDED) && !adv.switches.at(ADVSWITCH_PLAYER_DEAD) && adv.here >= 0);

  return adv;
}
